#########################################################################
# File : /lua/system/hook.lua
# Description : Hooking implementation
# Author : GPG
#########################################################################

--[[
Hooking an object method replaces the existing function with a table that
is callable exactly like the original function. Hook functions are stored
in a list on the metatable. When the object method is invoked the table call
iterates over the hooks calling them and then calls the original function.
--]]

function AddMethodHook(object,method,hook,hooker)
    local meta
    if type(object[method]) == "table" then
        meta = getmetatable(object[method])
        if not meta.hooks then
            WARN('Attempt to hook invalid table.')
            return
        end
    elseif type(object[method]) == "function" then
        local orig = object[method]
        
        meta = {
            hooks = {},
            origFunc = orig,
        }
        function meta:__call(...)
            for k,f in meta.hooks do
                f[1](f[2],unpack(arg))
            end
            meta.origFunc(unpack(arg))
        end
        object[method] = {}
        setmetatable(object[method],meta)
    else
        WARN('Attempt to hook a ',type(object[method]), ' type. Function required.')
        return
    end
    table.insert(meta.hooks,{hook, hooker})
end

function RemoveMethodHook(object,method,hook)
    if type(method) == "function" then
        WARN('Function is not hooked')
        return
    end
    
    local mt = getmetatable(object[method])
    if not mt.hooks then
        return
    end
    
    for k,v in mt.hooks do
        if v[1] == hook then
            table.remove(mt.hooks,k)
            break
        end
    end
    
    # If that was the last hook restore the original function
    if table.getn(mt.hooks) == 0 then
        object[method] = mt.origFunc
    end
end
